/*
 *  WsvJdbc - a JDBC driver for WSV (values separated by whatever) data files
 *  Copyright (C) 2004  Daniele Pes
 * 
 *  CsvJdbc - a JDBC driver for CSV files
 *  Copyright (C) 2001  Jonathan Ackerman
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
package axs.jdbc.driver;

import java.io.File;
import java.net.URL;
import java.net.URLConnection;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;

import axs.jdbc.utils.CharFormat;
import axs.jdbc.utils.DateAndTimeFormat;
import axs.jdbc.utils.UrlUtils;


/**
 * This class implements the Driver interface for the WsvJdbc driver.
 * 
 *  * This class is meant to test the WSV driver, its methods can be invoked by junit. WSV is a JDBC driver that allows to have a SQL interface
 * over data files. For a file of data you can specify a set of properties having to do with connection to the 
 * file of data and to data characteristics. These properties are:
 * - charset			   the set of characters data are written with.<br>
 * - fileExtension		this can be everything (it can even be empty).<br>
 * - separator				it is a single character (e.g.: ';' or '\t') or fixed_length (in this case data are not separated: each column has a fixed length set of characters in the data file).<br>
 * - suppressHeaders    this can be used to specify that the first line of the data file has not data but column names instead.<br>
 * - columnNames			they can be explicitly specified (e.g.: SOCIETA;CSAMPERS;COGNOME;NOME;HID;MAGNETICA) or desumed by the first line of the data file.<br>
 * - columnTypes        they can be explicitly specified as integer constants (e.g.: 4;12;12;12;12;12) according to this mapping: <br><br>
 *<table width="20%" border="1" align="center">
 *  <tr>     <td width="36%"><b>NAME</b></td>    <td width="50%"><b>Java type</b></td>    <td width="14%"><b>TYPE</b></td>  </tr>
 *  <tr>    <td width="36%">VARCHAR</td>    <td width="50%">String.class</td>    <td width="14%">12</td>  </tr>
 *  <tr>    <td width="36%">BOOLEAN</td>    <td width="50%">Boolean.class</td>    <td width="14%">16</td>  </tr>
 *  <tr>    <td width="36%">TINYINT</td>    <td width="50%">Byte.class</td>    <td width="14%">-6</td>  </tr>
 *  <tr>    <td width="36%">CHAR</td>    <td width="50%">Character.class</td>    <td width="14%">1</td>  </tr>
 *  <tr>    <td width="36%">DOUBLE</td>    <td width="50%">Double.class</td>    <td width="14%">8</td>  </tr>
 *  <tr>    <td width="36%">FLOAT</td>    <td width="50%">Float.class</td>    <td width="14%">6</td>  </tr>
 *  <tr>    <td width="36%">INTEGER</td>    <td width="50%">Integer.class</td>    <td width="14%">4</td>  </tr>
 *  <tr>    <td width="36%">BIGINT</td>    <td width="50%">Long.class</td>    <td width="14%">-5</td>  </tr>
 *  <tr>    <td width="36%">SMALLINT</td>    <td width="50%">Short.class</td>    <td width="14%">5</td>  </tr>
 *  <tr>    <td width="36%">TIMESTAMP</td>    <td width="50%">Timestamp.class</td>    <td width="14%">93</td>  </tr>
 *  <tr>    <td width="36%">DATE</td>    <td width="50%">Date.class</td>    <td width="14%">91</td>  </tr>
 *  <tr>    <td width="36%">TIME</td>    <td width="50%">Time.class</td>    <td width="14%">92</td>  </tr>
 *  <tr>    <td width="36%">VARBINARY</td>    <td width="50%">byte[].class</td>    <td width="14%">-3</td>  </tr>
 *  <tr>    <td width="36%">NULL</td>    <td width="50%">null</td>    <td width="14%">0</td>  </tr>
 *  <tr>    <td width="36%">JAVA_OBJECT</td>    <td width="50%">Object.class</td>    <td width="14%">2000</td>  </tr>
 *  <tr>    <td width="36%">P_BOOLEAN</td>    <td width="50%">boolean.class</td>    <td width="14%">100016</td>  </tr>
 *  <tr>    <td width="36%">P_TINYINT</td>    <td width="50%">byte.class</td>    <td width="14%">9994</td>  </tr>
 *  <tr>    <td width="36%">P_CHAR</td>    <td width="50%">char.class</td>    <td width="14%">100001</td>  </tr>
 *  <tr>    <td width="36%">P_DOUBLE</td>    <td width="50%">double.class</td>    <td width="14%">100008</td>  </tr>
 *  <tr>    <td width="36%">P_FLOAT</td>    <td width="50%">float.class</td>    <td width="14%">100006</td>  </tr>
 *  <tr>    <td width="36%">P_INTEGER</td>    <td width="50%">int.class</td>    <td width="14%">100004</td>  </tr>
 *  <tr>    <td width="36%">P_BIGINT</td>    <td width="50%">long.class</td>    <td width="14%">9995</td>  </tr>
 *  <tr>    <td width="36%">P_SMALLINT</td>    <td width="50%">short.class</td>    <td width="14%">100005</td>  </tr>
 *</table><br>
 * 							The types of columns can be specified either by their type integer identifiers or by their names.
 * 							If column types are not specified for the columns, they are assumed to be of type VARCHAR.<br>
 * - columnWidths			you can specify the maximum lenght of a VARCHAR column. In case of fixed lenght data, in the
 * 						   evaluated width of a column exceeds this width, then an exception is raised.<br>
 * - columnBoundaries   columns can be identified with fixed lenght mode. In this case each column hosts data in
 * 							a fixed lenght interval. In this case you must specify "fixed_length" as separator and define
 * 							the columns' intervals by their boundaries (e.g.: 1-3;4-6;7-16;17-22;23-23;24-33). Note: first
 * 					      character is at position 0.<br>
 * There are a couple of ways to get a connection to a plain text data file and to use it to query by SQL commands.
 * They are here described by examples:
 * (1st mode)
 * // (step 1) get a connection object passing the <code>URL</code> object pointing to the source (plain text) data file and
 * // the <code>Property</code> object holding the properties set according to the above described instructions
 * Connection conn = conn = DriverManager.getConnection(url, props);
 * // (step 2) create a Statement object to execute the query with
 * Statement stmt = conn.createStatement();
 * // (step 3)execute a query
 * ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM " + tableName);
 * <br>
 * <br>
 * (2nd mode: using the JdbcSourceConfiguration)
 * // (step 1) JdbcSourceConfiguration is useful to read the needed properties from a file and to have the correctly set
 * // and verified properties
 * JdbcSourceConfiguration cf = new JdbcSourceConfiguration("schema.ini");
 * Properties props = cf.getConnectionProperties();
 * // (step 2) load the Wsv driver. Note that you can also load it implicitly bu setting the system property jdbc.drivers like this:
 * // System.setProperty("jdbc.drivers", "axs.jdbc.driver.WsvDriver");
 * Class.forName(props.getProperty("jdbc.sourceDriver"));
 * // (steps 3, 4 and 5) like 1, 2 and 3 shown above 
 * 
 * @author     Jonathan Ackerman
 * @author     Sander Brienen
 * @author     JD Evora
 * @author     Tomasz Skutnik
 * @author 		Daniele Pes
 */
public class WsvDriver 
implements Driver {
	// default values
	public  static final String  DEFAULT_DATE_FORMAT = DateAndTimeFormat.ISO_FORMAT;
	public  static final String  DEFAULT_CHARSET	    = CharFormat.ISO_FORMAT;
	public  static final String  DEFAULT_EXTENSION   = ".csv";
   public  static final String  NO_EXTENSION        = "no_ext";
	public  static final char    DEFAULT_SEPARATOR   = ',';
	public  static final boolean DEFAULT_SUPPRESS    = true;
	// names of properties
	public static final String SKIP_LINES_PROP_NAME 		           = "skipLines";
	public static final String DATE_FORMAT_PROP_NAME		           = "dateFormat"; 	
	public static final String FILE_EXTENSION_PROP_NAME              = "fileExtension";
	public static final String SEPARATOR_PROP_NAME                   = "separator";
	public static final String SUPPRESS_HEADERS_PROP_NAME            = "suppressHeaders";
	public static final String CHARSET_PROP_NAME 			           = "charset";
	public static final String COLUMN_NAMES_PROP_NAME 	              = "columnNames";
	public static final String COLUMN_TYPES_PROP_NAME 	              = "columnTypes";
	public static final String COLUMN_WIDTHS_PROP_NAME 	           = "columnWidths";
	public static final String COLUMN_BOUNDARIES_PROP_NAME           = "columnBoundaries";
   public static final String COLUMN_FIXED_LENGTH_TO_TRIM_PROP_NAME = "fixedLengthVarcharsToBeTrimmed"; 
	
	private static final String  URL_PREFIX 		    = "jdbc:wsv:";        // wsv stands for "Whatever Separated Values"	


	/**
	 *Gets the propertyInfo attribute of the CsvDriver object
	 *
	 * @param  url               Description of Parameter
	 * @param  info              Description of Parameter
	 * @return                   The propertyInfo value
	 * @exception  SQLException  Description of Exception
	 * @since
	 */
	public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) 
	throws SQLException {
		return new DriverPropertyInfo[0];
	}


	/**
	 *Gets the majorVersion attribute of the WsvDriver object
	 *
	 * @return    The majorVersion value
	 * @since
	 */
	public int getMajorVersion() {
		return 1;
	}


	/**
	 *Gets the minorVersion attribute of the WsvDriver object
	 *
	 * @return    The minorVersion value
	 * @since
	 */
	public int getMinorVersion() {
		return 0;
	}


	/**
	 * (1) Check for correctness of url.
	 * (2) Get filepath from url.
	 * (3) Ends with file separator.
	 * (4) Formats the path according to the operating system.
	 * (5) Check validity of filepath.
	 * (6) Creates (and verifies) a new Wsv Connection.
	 * 
	 * @param     url                  The URL referring to the source data file.
	 * @param     dataSourceProperties This holds the properties that will bw used to create a new Csv connection.
	 * @return                         A new Csv Connection to source data file according to the properties held by dataSourceProperties. 
	 * @exception SQLException         Thrown if unable to open the needed connection to the source data file.
	 * @since
	 */
	public Connection connect(String     url, 
							        Properties dataSourceProperties) 
	throws SQLException {
		DriverManager.println("WSV-Jdbc - Whatever Separated Values Driver: connect() - url='" + url + "'");
		// (1)
		DriverManager.println("Looking for an URL pointing to file path and having structure like = " + url);
		if (url == null || !url.startsWith(URL_PREFIX)) {
			DriverManager.println("The specified URL format (" + url + ") is invalid");
			return null;
		}
		// (2)
		String filePath = url.substring(URL_PREFIX.length());
		// (3)
	    String separator = File.separator; // according to the underlying operating system
		if (!filePath.endsWith(separator)) {
			filePath += separator;
		}			
		// (4)		
		filePath = UrlUtils.getNormalizedPath(filePath);
		if(filePath == null)
			throw new SQLException("Specified path '" + filePath + "' is invalid !");
		DriverManager.println("WsvJdbc - WsvDriver:connect() - FILEPATH=" + filePath);
		// (5)
		URL checkURL;
		URLConnection cn;
		try {
			checkURL = UrlUtils.verifyAndSetUrl(filePath);									
			cn = checkURL.openConnection();						
		}
		catch (java.io.IOException ioe) {
			throw new SQLException("Specified path '" + filePath + "' not found ! Message was: " + ioe);
		}
		if(checkURL.getProtocol().equals(UrlUtils.FILE_PROTOCOL) && !(new File(checkURL.getPath()).isDirectory()))
			throw new SQLException("Specified path '" + filePath + "' doesn't point to a directory !");
		try {			 
			cn.getInputStream();
		}
		catch (java.io.IOException ioe) {
			throw new SQLException("Data file '" + checkURL.toExternalForm() + "' is unreadable ! (Message was: " + ioe.getMessage() + ")\nstack trace is:\n" + ioe.getStackTrace());
		}
		// (6)
		if(dataSourceProperties.getProperty(COLUMN_NAMES_PROP_NAME) == null || dataSourceProperties.getProperty(COLUMN_NAMES_PROP_NAME).equals(""))
			DriverManager.println("Column names are not available at connection time!");
		return new WsvConnection(filePath, dataSourceProperties);
	}


	/**
	 *Description of the Method
	 *
	 * @param  url               Description of Parameter
	 * @return                   Description of the Returned Value
	 * @exception  SQLException  Description of Exception
	 * @since
	 */
	public boolean acceptsURL(String url) throws SQLException {
		DriverManager.println("WsvJdbc - WsvDriver:accept() - URL=" + url);
		return url.startsWith(URL_PREFIX);
	}


	/**
	 *Description of the Method
	 *
	 * @return    Description of the Returned Value
	 * @since
	 */
	public boolean jdbcCompliant() {
		return false;
	}


	// This static block inits the driver when the class is loaded by the JVM.
	static {
		try {
			java.sql.DriverManager.registerDriver(new WsvDriver());
		}
		catch (SQLException e) {
			throw new RuntimeException("FATAL ERROR: Could not initialise CSV driver ! Message was: " + e.getMessage());
		}
	}


	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		// TODO Auto-generated method stub
		return null;
	}
	
}
